Skip to content

fix(sentryapps): Cache Sentry App installation lookups to prevent blocking RPCs#113493

Draft
sentry[bot] wants to merge 1 commit into
masterfrom
seer/fix-sentryapp-blocking-rpc
Draft

fix(sentryapps): Cache Sentry App installation lookups to prevent blocking RPCs#113493
sentry[bot] wants to merge 1 commit into
masterfrom
seer/fix-sentryapp-blocking-rpc

Conversation

@sentry
Copy link
Copy Markdown
Contributor

@sentry sentry Bot commented Apr 20, 2026

This PR addresses a 'Blocking Operation' performance issue (SENTRY-5NC9) occurring on /api/0/organizations/{organization_id_or_slug}/events-stats/ when accessed by Sentry Apps (e.g., Grafana integration proxy users).

Root Cause:
Each Sentry App request to a region endpoint was making two separate, synchronous cross-silo RPC HTTP calls to the control silo during the request lifecycle:

  1. app_service.get_installation_org_id_by_token_id (in ratelimits/utils.py for rate-limiting) - ~318ms blocking.
  2. app_service.find_installation_by_proxy_user (in auth/access.py for permission checks) - ~248ms blocking.
    These calls were necessary because Sentry App installation data resides in the control silo, and the region silo had no local cache for these specific lookups.

Solution:
This fix introduces Redis caching for the results of these two RPC calls in the region silo, significantly reducing latency for Sentry App requests.

Changes Made:

  • src/sentry/sentry_apps/services/app/service.py:
    • Introduced get_installation_org_id_by_token_id as a cached wrapper around the RPC call, using a Django cache key sentry-app-install-token:{token_id} with a 300s TTL.
    • Introduced get_installation_by_proxy_user as a cached wrapper around the RPC call, using a Django cache key sentry-app-install-proxy:{proxy_user_id}:{organization_id} with a 300s TTL.
    • Added _INSTALLATION_NOT_FOUND_SENTINEL and clear_installation_by_proxy_user_cache helper for cache management.
  • src/sentry/ratelimits/utils.py:
    • Updated get_organization_id_from_token to utilize the new cached get_installation_org_id_by_token_id function.
  • src/sentry/auth/access.py:
    • Updated _from_rpc_sentry_app to utilize the new cached get_installation_by_proxy_user function.
  • src/sentry/sentry_apps/models/sentry_app_installation.py:
    • Modified outboxes_for_delete to include proxy_user_id in the payload, enabling proper cache invalidation on deletion.
    • Added cache invalidation logic to handle_async_replication and handle_async_deletion to clear the sentry-app-install-proxy cache key when a SentryAppInstallation is updated or deleted.

Legal Boilerplate

Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. and is gonna need some rights from me in order to utilize my contributions in this here PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.

Fixes SENTRY-5NC9

@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Apr 20, 2026
@shayna-ch shayna-ch added the Trigger: getsentry tests Once code is reviewed: apply label to PR to trigger getsentry tests label Apr 20, 2026
payload={
"sentry_app_id": self.sentry_app_id,
"organization_id": self.organization_id,
"proxy_user_id": self.sentry_app.proxy_user_id,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SentryApp.DoesNotExist not handled in outboxes_for_delete when accessing proxy_user_id

The new code at line 170 accesses self.sentry_app.proxy_user_id without handling SentryApp.DoesNotExist. Since SentryApp uses ParanoidModel (soft-delete), the parent SentryApp may be soft-deleted before the installation's outboxes_for_delete is called. The same file already demonstrates this pattern at lines 146-149 where api_application_id catches SentryApp.DoesNotExist. This will cause deletion to fail with an unhandled exception when the SentryApp has been deleted first.

Verification

Read sentry_app_installation.py to verify: 1) SentryApp FK relationship at line 72, 2) Existing DoesNotExist handling pattern at lines 146-149 for similar access to self.sentry_app.application_id, 3) SentryApp is a ParanoidModel (soft-delete) confirmed in sentry_app.py line 91. The same exception risk applies to proxy_user_id access.

Identified by Warden sentry-backend-bugs · JDV-DGX

@shayna-ch shayna-ch self-assigned this Apr 20, 2026
@getsantry
Copy link
Copy Markdown
Contributor

getsantry Bot commented May 12, 2026

This pull request has gone three weeks without activity. In another week, I will close it.

But! If you comment or otherwise update it, I will reset the clock, and if you add the label WIP, I will leave it alone unless WIP is removed ... forever!


"A weed is but an unloved flower." ― Ella Wheeler Wilcox 🥀

@getsantry getsantry Bot added the Stale label May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components Stale Trigger: getsentry tests Once code is reviewed: apply label to PR to trigger getsentry tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant